using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;

namespace GameFrame
{
    public static class Native
    {
        public static Native.Utility Utils;

        public static int RoundUpToAlignment(int size, int alignment)
        {
            switch (alignment)
            {
                case 1:
                    return size;
                case 2:
                    return (size + 1 >> 1) * 2;
                case 4:
                    return (size + 3 >> 2) * 4;
                case 8:
                    return (size + 7 >> 3) * 8;
                case 16:
                    return (size + 15 >> 4) * 16;
                case 32:
                    return (size + 31 >> 5) * 32;
                case 64:
                    return (size + 63 >> 6) * 64;
                default:
                    throw new InvalidOperationException(string.Format("Invalid Alignment: {0}", (object) alignment));
            }
        }

        public static int GetAlignmentForArrayElement(int elementSize)
        {
            Assert.Check(elementSize > 0);
            return elementSize > 0 && elementSize % 8 == 0 ? 8 : 4;
        }

        public struct ObjectHandle
        {
            public readonly IntPtr Address;
            public readonly ulong Identifier;

            public ObjectHandle(IntPtr address)
            {
                this.Address = address;
                this.Identifier = 0UL;
            }

            public ObjectHandle(ulong identifier)
            {
                this.Address = IntPtr.Zero;
                this.Identifier = identifier;
            }
        }

        public abstract class Allocator
        {
            private readonly HashSet<IntPtr> _allocated = new HashSet<IntPtr>();

            protected unsafe void TrackAlloc(void* ptr)
            {
                lock (this._allocated)
                    this._allocated.Add((IntPtr) ptr);
            }

            protected unsafe void TrackFree(void* ptr)
            {
                lock (this._allocated)
                {
                    if (!this._allocated.Remove((IntPtr) ptr))
                        throw new Exception(string.Format("Tried to free {0} which was not allocated", (object) (IntPtr) ptr));
                }
            }

            public void Dispose()
            {
                lock (this._allocated)
                {
                    if (this._allocated.Count <= 0)
                        return;
                    Log.Error(string.Format("Found {0} allocations which leaked memory", (object) this._allocated.Count), Array.Empty<object>());
                    foreach (IntPtr num in this._allocated)
                        Log.Error(string.Format("Undisposed Memory: {0}", (object) num), Array.Empty<object>());
                }
            }

            public abstract unsafe void Free(void* ptr);

            public abstract unsafe void* Alloc(int count);

            public abstract unsafe void* Alloc(int count, int alignment);

            protected abstract unsafe void Clear(void* dest, int count);

            public unsafe void* AllocAndClear(int count)
            {
                void* dest = this.Alloc(count);
                this.Clear(dest, count);
                return dest;
            }

            public unsafe void* AllocAndClear(int count, int alignment)
            {
                void* dest = this.Alloc(count, alignment);
                this.Clear(dest, count);
                return dest;
            }

            public unsafe T* AllocAndClear<T>() where T : unmanaged
            {
                void* dest = this.Alloc(sizeof (T));
                this.Clear(dest, sizeof (T));
                return (T*) dest;
            }

            public unsafe T* Alloc<T>() where T : unmanaged => (T*) this.Alloc(sizeof (T));

            public unsafe void* Expand(void* buffer, int currentSize, int newSize)
            {
                Assert.Check(newSize > currentSize);
                void* voidPtr = buffer;
                void* dest = this.AllocAndClear(newSize);
                Native.Utils.Copy(dest, voidPtr, currentSize);
                this.Free(voidPtr);
                return dest;
            }

            public unsafe T* ExpandArray<T>(T* buffer, int currentSize, int newSize) where T : unmanaged
            {
                Assert.Check(newSize > currentSize);
                T* objPtr = buffer;
                T* dest = (T*) this.AllocAndClear(sizeof (T) * newSize);
                Native.Utils.Copy((void*) dest, (void*) objPtr, sizeof (T) * currentSize);
                this.Free((void*) objPtr);
                return dest;
            }
        }

        public abstract class Utility
        {
            public abstract Native.ObjectHandle HandleAcquire(object obj);

            public abstract void HandleRelease(Native.ObjectHandle handle);

            public abstract object GetObjectForHandle(Native.ObjectHandle handle);

            public abstract unsafe void Copy(void* dest, void* src, int count);

            public abstract unsafe void Clear(void* dest, int count);

            public abstract unsafe void Move(void* dest, void* src, int count);

            public abstract unsafe int Compare(void* ptr1, void* ptr2, int count);

            public unsafe void CopyArrayWithStride(
                void* source,
                int sourceIndex,
                void* destination,
                int destinationIndex,
                int count,
                int stride)
            {
                this.Copy((void*) ((IntPtr) destination + destinationIndex * stride), (void*) ((IntPtr) source + sourceIndex * stride), count * stride);
            }

            public unsafe void CopyArray<T>(
                T* source,
                int sourceIndex,
                T* destination,
                int destinationIndex,
                int count)
                where T : unmanaged
            {
                this.CopyArrayWithStride((void*) source, sourceIndex, (void*) destination, destinationIndex, count, sizeof (T));
            }

            public static unsafe void ClearFast(void* dest, int count) => Native.Utils.Clear(dest, count);

            public static unsafe void CopyFast(void* dest, void* src, int count) => Native.Utils.Copy(dest, src, count);
        }
        
        [SuppressUnmanagedCodeSecurity]
        public class LIBCAllocator : Native.PInvokeAllocator
        {
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memset(IntPtr dest, int c, UIntPtr byteCount);

            protected override sealed unsafe void Clear(void* dest, int count)
            {
                Assert.Check(count >= 0);
                Native.LIBCAllocator.memset((IntPtr) dest, 0, (UIntPtr) (ulong) count);
            }
        }

        [SuppressUnmanagedCodeSecurity]
        public class LIBCUtility : Native.PInvokeUtility
        {
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memmove(IntPtr dest, IntPtr src, UIntPtr count);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memset(IntPtr dest, int c, UIntPtr byteCount);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libc", CallingConvention = CallingConvention.Cdecl)]
            private static extern int memcmp(IntPtr ptr1, IntPtr ptr2, UIntPtr byteCount);

            public override sealed unsafe void Clear(void* dest, int count)
            {
                Assert.Check(count >= 0);
                Native.LIBCUtility.memset((IntPtr) dest, 0, (UIntPtr) (ulong) count);
            }

            public override sealed unsafe void Copy(void* dest, void* src, int count)
            {
                Assert.Check(count >= 0);
                Native.LIBCUtility.memcpy((IntPtr) dest, (IntPtr) src, (UIntPtr) (ulong) count);
            }

            public override sealed unsafe void Move(void* dest, void* src, int count)
            {
                Assert.Check(count >= 0);
                Native.LIBCUtility.memmove((IntPtr) dest, (IntPtr) src, (UIntPtr) (ulong) count);
            }

            public override sealed unsafe int Compare(void* ptr1, void* ptr2, int count)
            {
                Assert.Check(count >= 0);
                return Native.LIBCUtility.memcmp((IntPtr) ptr1, (IntPtr) ptr2, (UIntPtr) (ulong) count);
            }
        }

        [SuppressUnmanagedCodeSecurity]
        public sealed class MSVCRTAllocator : Native.PInvokeAllocator
        {
            [SuppressUnmanagedCodeSecurity]
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memset(IntPtr dest, int c, UIntPtr byteCount);

            protected override sealed unsafe void Clear(void* dest, int count)
            {
                Assert.Check(count >= 0);
                Native.MSVCRTAllocator.memset((IntPtr) dest, 0, (UIntPtr) (ulong) count);
            }
        }

        [SuppressUnmanagedCodeSecurity]
        public sealed class MSVCRTUtility : Native.PInvokeUtility
        {
            [SuppressUnmanagedCodeSecurity]
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memcpy(IntPtr dest, IntPtr src, UIntPtr count);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memmove(IntPtr dest, IntPtr src, UIntPtr count);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            private static extern IntPtr memset(IntPtr dest, int c, UIntPtr byteCount);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
            private static extern int memcmp(IntPtr ptr1, IntPtr ptr2, UIntPtr count);

            public override sealed unsafe void Clear(void* dest, int count)
            {
                Assert.Check(count >= 0);
                Native.MSVCRTUtility.memset((IntPtr) dest, 0, (UIntPtr) (ulong) count);
            }

            public override sealed unsafe void Copy(void* dest, void* src, int count)
            {
                Assert.Check(count >= 0);
                Native.MSVCRTUtility.memcpy((IntPtr) dest, (IntPtr) src, (UIntPtr) (ulong) count);
            }

            public override sealed unsafe void Move(void* dest, void* src, int count)
            {
                Assert.Check(count >= 0);
                Native.MSVCRTUtility.memmove((IntPtr) dest, (IntPtr) src, (UIntPtr) (ulong) count);
            }

            public override sealed unsafe int Compare(void* ptr1, void* ptr2, int count)
            {
                Assert.Check(count >= 0);
                return Native.MSVCRTUtility.memcmp((IntPtr) ptr1, (IntPtr) ptr2, (UIntPtr) (ulong) count);
            }
        }

        [SuppressUnmanagedCodeSecurity]
        public abstract class PInvokeUtility : Native.Utility
        {
            private static void VerifyHandle(Native.ObjectHandle handle)
            {
                if (handle.Address == IntPtr.Zero)
                    throw new InvalidOperationException("ObjectHandle.Address is null");
                if (handle.Identifier > 0UL)
                    throw new InvalidOperationException("ObjectHandle.Identifier has to be 0");
            }

            public override Native.ObjectHandle HandleAcquire(object obj) => new Native.ObjectHandle(GCHandle.Alloc(obj, GCHandleType.Pinned).AddrOfPinnedObject());

            public override void HandleRelease(Native.ObjectHandle handle)
            {
                Native.PInvokeUtility.VerifyHandle(handle);
                GCHandle.FromIntPtr(handle.Address).Free();
            }

            public override object GetObjectForHandle(Native.ObjectHandle handle)
            {
                Native.PInvokeUtility.VerifyHandle(handle);
                return GCHandle.FromIntPtr(handle.Address).Target;
            }
        }

        [SuppressUnmanagedCodeSecurity]
        public abstract class PInvokeAllocator : Native.Allocator
        {
            private Dictionary<IntPtr, IntPtr> _alignmentMapping = new Dictionary<IntPtr, IntPtr>();

            public override sealed unsafe void* Alloc(int count)
            {
                if (count == 0)
                    return (void*) null;
                void* ptr = (void*) Marshal.AllocHGlobal((IntPtr) count);
                this.TrackAlloc(ptr);
                return ptr;
            }

            public override sealed unsafe void* Alloc(int count, int alignment)
            {
                byte* ptr = (byte*) (void*) Marshal.AllocHGlobal(count + alignment);
                long num1 = (long) ptr % (long) alignment;
                long num2 = (long) alignment - num1;
                this.TrackAlloc((void*) ptr);
                Assert.Check(num2 >= 0L && num2 <= (long) alignment);
                if (num1 == 0L)
                    return (void*) ptr;
                lock (this._alignmentMapping)
                    this._alignmentMapping.Add(new IntPtr((void*) (ptr + num2)), new IntPtr((void*) ptr));
                return (void*) (ptr + num2);
            }

            public override sealed unsafe void Free(void* ptr)
            {
                if (this._alignmentMapping.Count > 0)
                {
                    lock (this._alignmentMapping)
                    {
                        if (this._alignmentMapping.Count > 0)
                        {
                            IntPtr num;
                            if (this._alignmentMapping.TryGetValue(new IntPtr(ptr), out num))
                                ptr = num.ToPointer();
                        }
                    }
                }
                this.TrackFree(ptr);
                Marshal.FreeHGlobal((IntPtr) ptr);
            }
        }
    }
}